/*
*
==============================================================================
*  Name        : 
*  Part of     : Omen Game Engine - Autodesk 3ds Studio .3ds file loader
*  Description : 
*  Version     : 1.0
*
*  Copyright (c) 2013 Lauri Kortevaara.
* 
==============================================================================
*/

#define PLUGIN_3DS_SOURCE 1

#include "Model_3ds_Reader.h"
#include <IOmenMesh.h>

#include <string>
#include <fstream>
#include <iostream>
#include <ios>
#include <vector>
#include <algorithm>

using namespace Omen;
using namespace Model3DS;

/// Data reading functions
BYTE GetBYTE( BYTE* ptr );
WORD GetWORD( BYTE* ptr );
DWORD GetDWORD( BYTE* ptr );
float GetFloat( BYTE* ptr );
void GetVector( BYTE* ptr, Omen::Vector3d& );


/// <summary>Default Constructor</summary>
Model_3DS_Reader::Model_3DS_Reader() :
        m_fileName(L""),
        m_buffer(NULL),
        m_bufferSize(0),
        m_pCurrentScene(NULL),
        m_pCurrentModel(NULL),
        m_pCurrentMesh(NULL),
        m_pCurrentMaterial(NULL)
{
}

/// <summary>Constructor with a filename</summary>
Model_3DS_Reader::Model_3DS_Reader( const std::wstring& filename ) :
    m_fileName(filename),
    m_buffer(NULL),
    m_bufferSize(0),
    m_pCurrentScene(NULL),
    m_pCurrentModel(NULL),
    m_pCurrentMesh(NULL),
    m_pCurrentMaterial(NULL)
{
}

/// <summary>Copy constructor</summary>
Model_3DS_Reader::Model_3DS_Reader( const Model_3DS_Reader& other ) 
{
    m_pCurrentScene = NULL;
    m_pCurrentModel = NULL;
    m_pCurrentMesh = NULL;
    m_pCurrentMaterial = NULL;

    m_fileName = other.m_fileName;

    m_bufferSize = other.m_bufferSize;
    m_buffer = new BYTE [static_cast<unsigned int>(m_bufferSize)];
}

/// <summary>Destroys a 3ds archive reader</summary>
Model_3DS_Reader::~Model_3DS_Reader() 
{
    if( m_buffer )
        delete[] m_buffer;
    
    m_buffer = NULL;
    m_bufferSize = 0;
}

/// <summary>Returns true if this reader can read the given file</summary>
bool Model_3DS_Reader::canRead( const std::wstring& filename )
{
    bool bCanRead = false;

    if( filename.find (L".3ds" ) != std::wstring::npos )
    {
        BYTE* buffer;
        std::streamoff size;
        std::ifstream file (filename, std::ios::in|std::ios::binary|std::ios::ate);
        size = file.tellg();
        file.seekg (0, std::ios::beg);
        buffer = new BYTE [static_cast<unsigned char>(2)];
        if( buffer )
        {
            file.read ( reinterpret_cast<char*>(buffer), 2); // Read only two bytes from the start of the file
            file.close();
            WORD chunkId = GetWORD(buffer);
            if( chunkId == Model3DS::MAIN3DS )
            {
                m_fileName = filename;
                bCanRead = true;
            }
            delete[] buffer;
        }
    }

    return bCanRead;
}





/// <summary>Reads the contents of the model file</summary>
bool Model_3DS_Reader::loadFile()
{
    bool bSuccess = false;
    if( !m_fileName.empty() )
    {
        std::ifstream file (m_fileName, std::ios::in|std::ios::binary|std::ios::ate);
        if( file.good() )
        {
            m_bufferSize = static_cast<unsigned long>(file.tellg());
            file.seekg (0, std::ios::beg);
            m_buffer = new BYTE [static_cast<unsigned int>(m_bufferSize)];
            if( m_buffer )
            {
                file.read ( reinterpret_cast<char*>(m_buffer), m_bufferSize); // Read the whole file in buffer
                file.close();
        
                WORD chunkId = GetWORD(m_buffer);
                BYTE* ptrChunk = m_buffer;
                Chunk chunk;
                while( readNextChunk( ptrChunk, &chunk ) == true && ptrChunk < (m_buffer+m_bufferSize));
            }
        }else throw std::runtime_error( "Model_3DS_Reader: Cannot open file!" );
        bSuccess = true;
    }
    return bSuccess;
}

bool Model_3DS_Reader::getChunkData( BYTE* ptrChunk, Chunk* pChunk )
{
    bool bSuccess = false;
    if( ptrChunk )
    {
        WORD  chunkId = GetWORD(ptrChunk);
        DWORD chunkLength = GetDWORD(ptrChunk+2);
        
        pChunk->chunkId = chunkId;
        pChunk->chunkLength = chunkLength;
        pChunk->ptrChunk = ptrChunk;
        pChunk->ptrChunkData = ptrChunk+6;
        //std::cout << "Read chunk: " << std::hex << chunkId << ", length: " << chunkLength << std::dec << std::endl;
    }
    return bSuccess;
}

bool Model_3DS_Reader::readMaterialChunk( BYTE*& ptrChunk, Chunk* pChunk )
{
    bool bSuccess = false;
    getChunkData(ptrChunk, pChunk);
    unsigned long bufDelta = ptrChunk - this->m_buffer;

    WORD chunkId = pChunk->chunkId;

    if( chunkId >> 12 != 0xa )
        return false;

    // If were dealing with a material, jump right to the first material parameter
    if( chunkId == EDIT_MATERIAL )
    {
        m_pCurrentMaterial = factory().newMaterialObject();
        m_listMaterials.push_back(m_pCurrentMaterial);
        ptrChunk = pChunk->ptrChunkData;
        getChunkData(ptrChunk, pChunk);
    }

    Chunk subChunk;
    BYTE* ptrSubChunk = ptrChunk;
    getChunkData(ptrSubChunk, &subChunk);
    WORD subChunkId = subChunk.chunkId;
    BYTE* subChunkData = subChunk.ptrChunkData;
    ptrChunk = ptrSubChunk+subChunk.chunkLength;
    switch( subChunkId )
    {
      // ASCII name
    case EDIT_MATERIAL_NAME:
    {
        char* materialName = (char*)subChunkData;
        std::cout << "Found a material named: " << materialName << std::endl;
        if( m_pCurrentMaterial )
        {
            std::string strName = materialName;
            m_pCurrentMaterial->name() = std::wstring(strName.begin(), strName.end());
            m_pCurrentMaterial->textureFilename() = std::wstring(strName.begin(), strName.end());
            m_pCurrentMaterial->textureFilename().append( L".jpg" );
        }
        break;
    }
      // RGB1 and RGB2 
    case EDIT_MATERIAL_AMBIENT_COLOR:
    {
        BYTE* ptrColor = subChunk.ptrChunkData;
        WORD colorSubId = GetWORD(ptrColor);
        ptrColor += sizeof(WORD);
        BYTE colorSubLen = GetBYTE(ptrColor);
        
        ptrColor += sizeof(BYTE);
        BYTE redGamma = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        BYTE greenGamma = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        BYTE blueGamma = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        
        BYTE red = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        BYTE green = GetBYTE( ptrColor);
        ptrColor += sizeof(BYTE);
        BYTE blue = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);

        if( m_pCurrentMaterial )
        {
            m_pCurrentMaterial->ambientColor() = Vector4d( red/255.0f, green/255.0f, blue/255.0f, 1.0f );
        }
        break;
    }
      // idem
    case EDIT_MATERIAL_DIFFUSER_COLOR:
    {
        BYTE* ptrColor = subChunk.ptrChunkData;
        WORD colorSubId = GetWORD(ptrColor);
        ptrColor += sizeof(WORD);
        BYTE colorSubLen = GetBYTE(ptrColor);
        
        ptrColor += sizeof(BYTE);
        BYTE redGamma = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        BYTE greenGamma = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        BYTE blueGamma = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        
        BYTE red = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        BYTE green = GetBYTE( ptrColor);
        ptrColor += sizeof(BYTE);
        BYTE blue = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        if( m_pCurrentMaterial )
        {
            m_pCurrentMaterial->diffuseColor() = Vector4d( red/255.0f, green/255.0f, blue/255.0f, 1.0f );
        }
      break;
    }
      // idem
    case EDIT_MATERIAL_SPECULAR_COLOR:
    {
        BYTE* ptrColor = subChunk.ptrChunkData;
        WORD colorSubId = GetWORD(ptrColor);
        ptrColor += sizeof(WORD);
        BYTE colorSubLen = GetBYTE(ptrColor);
        
        ptrColor += sizeof(BYTE);
        BYTE redGamma = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        BYTE greenGamma = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        BYTE blueGamma = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        
        BYTE red = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        BYTE green = GetBYTE( ptrColor);
        ptrColor += sizeof(BYTE);
        BYTE blue = GetBYTE( ptrColor );
        ptrColor += sizeof(BYTE);
        if( m_pCurrentMaterial )
        {
            m_pCurrentMaterial->specularColor() = Vector4d( red/255.0f, green/255.0f, blue/255.0f, 1.0f );
        }
      break;
    }
      // amount of 
    case EDIT_MATERIAL_SHININESS:
    {
        Omen::Float shininess = GetFloat(subChunk.ptrChunkData);
        if( m_pCurrentMaterial )
            m_pCurrentMaterial->shininess() = shininess;
        break;
    }
      // ""
    case EDIT_MATERIAL_SHINE_STRENGTH:
    {
      break;
    }
      // ""
    case EDIT_MATERIAL_TRANSPARENCY:
    {
      break;
    }
      // ""
    case EDIT_MATERIAL_TRANSPARENCY_FALLOFF:
    {
      break;
    }
      // ""
    case EDIT_MATERIAL_REFLECTION_BLUR:
    {
      break;
    }
      // 1=flat, 2=goraud, 3=phong, 4=metal
    case EDIT_MATERIAL_TYPE:
    {
      break;
    }
      // self illum      [amount of]
    case EDIT_MATERIAL_SELF_ILLUM:
    {
      break;
    }
      // some trans. falloff amount (not 0)
    case EDIT_MATERIAL_TRANSPARENCY_FALLOFF_A:
    {
      break;
    }
    // some reflect. blur
    case EDIT_MATERIAL_REFLECTION_BLUR_A:
    {
      break;
    }
    // two sided
    case EDIT_MATERIAL_TWO_SIDED:
    {
      break;
    }
    // transparency ADD
    case EDIT_MATERIAL_TRANSPARENCY_ADD:
    {
      break;
    }
    // wire on
    case EDIT_MATERIAL_WIREFRAME_ON:
    {
      break;
    }
    // face map
    case EDIT_MATERIAL_FACE_MAP:
    {
      break;
    }
    // trans. falloff IN
    case EDIT_MATERIAL_TRANSPARENCY_FALLOFF_IN:
    {
      break;
    }
    // soften
    case EDIT_MATERIAL_SOFTEN:
    {
      break;
    }
    // 3d wire thickness in units (default is in pix)
    case EDIT_MATERIAL_WIREFRAME_THICKNESS_IN_UNITS:
    {
      break;
    }
    // float    wire thickness (always present, in case of forced wire rendering)
    case EDIT_MATERIAL_WIREFRAME_THICKNESS:
    {
      break;
    }
    // Texture map
    case EDIT_MATERIAL_TEXTURE1:
    {
        BYTE* ptrTexture = subChunk.ptrChunkData;
        WORD textureSubId = GetWORD(ptrTexture);
        ptrTexture += sizeof(WORD);
        DWORD textureSubLen = GetDWORD(ptrTexture);
        ptrTexture += sizeof(DWORD);
        char* textureFilename = (char*)(ptrTexture);
        std::string strTextureName = textureFilename;
        std::wstring wstrTextureName = std::wstring( strTextureName.begin(), strTextureName.end() ); 
        if( m_pCurrentMaterial )
        {
            std::unique_ptr<Omen::ITexture> texture = GetOmenFactory().newTextureObject();
            m_pCurrentMaterial->addTexture(std::move(texture));
            m_pCurrentTexture = texture.get();
            m_pCurrentMaterial->textureFilename() = wstrTextureName;
            std::wcout << L"\t\tFound a texture file property: " << wstrTextureName << L" for material named: " << m_pCurrentMaterial->name() << std::endl;
        }
        break;
    }
    case EDIT_MATERIAL_TEXTURE_MAP_TILING:
    {
        WORD tilingFlag = GetWORD(subChunk.ptrChunkData);
        
        break;
    }
    case EDIT_MATERIAL_TEXTURE_MAP_USCALE:
    {
        break;
    }
    case EDIT_MATERIAL_TEXTURE_MAP_VSCALE:
    {
        break;
    }
    case EDIT_MATERIAL_TEXTURE_MAP_UOFFSET:
    {
        break;
    }
    case EDIT_MATERIAL_TEXTURE_MAP_VOFFSET:
    {
        break;
    }
    case EDIT_MATERIAL_TEXTURE_MAP_ROTATION:
    {
        break;
    }
    case EDIT_MATERIAL_TEXTURE_FILENAME:
    {
        break;
    }
    } // ~Switch chunk id

    BYTE* bufLast = m_buffer+m_bufferSize;
    if( ptrChunk < bufLast )
    {
        Chunk nextChunk;
        readMaterialChunk( ptrChunk, &nextChunk );
    }

    return true;
}

bool IMaterialComparator(Omen::IMaterial const* obj, std::wstring name) {
    return obj->name() == name;
}

bool Model_3DS_Reader::readSubChunk( BYTE*& ptrChunk, Chunk* pChunk )
{
    bool bSuccess = false;
    BYTE* bufLast = m_buffer+m_bufferSize;
    std::vector<WORD> faceIndexRef;

    do {
        getChunkData(ptrChunk, pChunk);
        unsigned long bufDelta = ptrChunk - this->m_buffer;

        if( pChunk->chunkId == EDIT_OBJECT ) // An object block
        {
            const char* objName = reinterpret_cast<const char*>(pChunk->ptrChunkData);
            ptrChunk = pChunk->ptrChunkData;
            while( *ptrChunk ) ++ptrChunk; // To the next Chunk, as string ends in 0x00
            //and then one byte forward
            ++ptrChunk;

            if( m_pCurrentScene )
            {
                Omen::IModel* pModel = newModel();
                std::string strName(objName);
                std::wstring modelName(strName.begin(),strName.end());
                pModel->setProperty( L"name", modelName );
                m_pCurrentScene->addModel(pModel);
                //std::cout << "Added new model " << std::hex << pModel << " to the current scene " << m_pCurrentScene << std::dec << std::endl;
                //std::cout << "Model count now " << m_pCurrentScene->modelCount() << std::endl;
                m_pCurrentModel = pModel;
            }
        }
        else
        if( pChunk->chunkId == OBJ_TRIMESH )
        {
            ptrChunk += ChunkHeaderSize;
            if( m_pCurrentModel )
            {
                Omen::IMesh* pMesh = newMesh();
                m_pCurrentModel->addMesh(pMesh);
                //std::cout << "Added new mesh " << std::hex << pMesh << " to the current model " << m_pCurrentModel << std::dec << std::endl;
                //std::cout << "Mesh count now " << m_pCurrentModel->meshCount() << std::endl;
                m_pCurrentMesh = pMesh;
                
            }
        }
        else
        if( pChunk->chunkId == TRI_VERTEXL )
        {
            WORD numberOfVertices = GetWORD( pChunk->ptrChunkData );
            ptrChunk = pChunk->ptrChunkData;
            ptrChunk += sizeof(WORD); // skip the num vertices
        
            //std::cout << "Starting to read 3ds file vertices at pos: " << std::hex << bufDelta << std::dec << std::endl;
            for( unsigned int i=0; i < numberOfVertices; ++i )
            {
                Omen::IMesh::Vertex v;
                v.x = GetFloat(ptrChunk);
                ptrChunk += sizeof(float);
                v.z = GetFloat(ptrChunk);
                ptrChunk += sizeof(float);
                v.y = GetFloat(ptrChunk);
                ptrChunk += sizeof(float);
                if( m_pCurrentMesh )
                {
                    m_pCurrentMesh->addVertex(v);
                    //std::cout << "New vertex: #" << i << " [" << v.x << ", " << v.y << ", " << v.z << "]" << std::endl;
                }
                bufDelta = ptrChunk - this->m_buffer;
            }
            m_pCurrentMesh->centralizeVertices();
            //std::cout << "Added new vertices to the current mesh " << m_pCurrentMesh << std::dec << std::endl;
            //std::cout << "Vertex count now " << m_pCurrentMesh->vertexCount() << std::endl << std::endl;
            //std::cout << "End of readind 3ds file vertices at pos: " << std::hex << bufDelta << std::dec << std::endl;
        }
        else
        if( pChunk->chunkId == TRI_FACEL1 )
        {
            WORD numberOfFaces = GetWORD( pChunk->ptrChunkData );
            ptrChunk = pChunk->ptrChunkData;
            ptrChunk += sizeof(WORD); // skip the num vertices
        
            //std::cout << "Starting to read 3ds file: " << numberOfFaces << " faces at pos: " << std::hex << bufDelta << std::dec << std::endl;
            for( unsigned int i=0; i < numberOfFaces; ++i )
            {
                WORD v1, v2, v3, faceFlag;
                v1 = GetWORD(ptrChunk);
                ptrChunk += sizeof(WORD);
                v2 = GetWORD(ptrChunk);
                ptrChunk += sizeof(WORD);
                v3 = GetWORD(ptrChunk);
                ptrChunk += sizeof(WORD);
                faceFlag = GetWORD(ptrChunk);
                if( faceFlag )
                {
                    bool acVisible = (faceFlag & 1) != 0;
                    bool bcVisible = (faceFlag & 2) != 0;
                    bool abVisible = (faceFlag & 4) != 0;
                    /*
                     bit 0       AC visibility
                     bit 1       BC visibility
                     bit 2       AB visibility
                     bit 3       Mapping (if there is mapping for this face)
                     bit 4-8   0 (not used ?)
                     bit 9-10  x (chaotic ???)
                     bit 11-12 0 (not used ?)
                     bit 13      face selected in selection 3
                     bit 14      face selected in selection 2
                     bit 15      face selected in selection 1
                     */
                }
                ptrChunk += sizeof(WORD);
                if( m_pCurrentMesh )
                {
                    m_pCurrentMesh->addFace(v3, v2, v1);
                    //std::cout << "New vertex: #" << i << " [" << v.x << ", " << v.y << ", " << v.z << "]" << std::endl;
                }
                bufDelta = ptrChunk - this->m_buffer;
            }
            //std::cout << "End of readind 3ds file faces at pos: " << std::hex << bufDelta << std::dec << std::endl;
        }
        else
        if( pChunk->chunkId == TRI_MAPPING_COORD )
        {
            ptrChunk = pChunk->ptrChunkData;
            WORD vertexCount = GetWORD(ptrChunk);
            ptrChunk += sizeof(WORD);
            for( unsigned int i=0; i < vertexCount; ++i )
            {
                float U = GetFloat(ptrChunk);
                ptrChunk += sizeof(DWORD);
                float V = GetFloat(ptrChunk);
                ptrChunk += sizeof(DWORD);

                std::vector<Omen::IMesh::Vertex>::iterator v;
                m_pCurrentMesh->getVertex(i, v);
                v->tu = U;
                v->tv = V;
            }
        }
        else
        if( pChunk->chunkId == EDIT_MESH_VERSION )
        {
            DWORD meshVersion = GetWORD( pChunk->ptrChunkData );
            //std::cout << "Mesh version: " << meshVersion << std::endl;
            ptrChunk = pChunk->ptrChunkData;
            ptrChunk += sizeof(DWORD);
        }
        else
        if( pChunk->chunkId == EDIT_CONFIG1 )
        {
            float oneUnit = GetFloat( pChunk->ptrChunkData );
            //std::cout << "One Unit is: " << oneUnit << std::endl;
            ptrChunk = pChunk->ptrChunkData;
            ptrChunk += sizeof(DWORD);
        }
        else
        if( pChunk->chunkId == TRI_LOCAL )     // Local coordinate system
        {
            BYTE* ptrData = pChunk->ptrChunkData;
            //std::cout << "Size of float: " << sizeof(float) << ", sizeof double: " << sizeof(double) << ", sizeof DWORD: " << sizeof(DWORD) << std::endl;
            Omen::Vector3d x,y,z, origo;
            GetVector( ptrData, x );
            ptrData += 3*sizeof(float);
            GetVector( ptrData, y );
            ptrData += 3*sizeof(float);
            GetVector( ptrData, z );
            ptrData += 3*sizeof(float);
            GetVector( ptrData, origo );
            ptrData += 3*sizeof(float);
            
            ptrChunk = pChunk->ptrChunkData+48; // This is the size of the Chunk-data
            Omen::Matrix4x4 t;
            t.setColumn(0, x);
            t.setColumn(1, y);
            t.setColumn(2, z);
            t.setRow( 3, origo );
            //m_pCurrentModel->setTransformation(t);
            //std::cout << "Local Coordinate System X1=" << X1 << ", X2=" << X2 << ", X3=" << X3 << ", O=" << O << std::endl;
        }
        else
        if( pChunk->chunkId == TRI_FACEMATL )
        {
            ptrChunk = pChunk->ptrChunkData;
            const char* materialName = reinterpret_cast<const char*>(pChunk->ptrChunkData);
            std::string strMaterialName = materialName;
            std::wstring wstrMaterialName( strMaterialName.begin(), strMaterialName.end() );
            while( *ptrChunk ) ++ptrChunk; // To the next Chunk, as string ends in 0x00
            ++ptrChunk;
            WORD faceCount = GetWORD(ptrChunk);
            ptrChunk += sizeof(WORD);
            // Clear previous face references
            faceIndexRef.clear();
            BYTE* endPtr = ptrChunk + sizeof(WORD)*faceCount;
            // Add new ones.
            if( m_pCurrentMesh )
            {
                m_pCurrentMesh->setProperty( L"texture", wstrMaterialName );
                auto material = std::find_if(m_listMaterials.begin(), m_listMaterials.end(), std::bind2nd(std::ptr_fun(IMaterialComparator),wstrMaterialName) );
                if( material != m_listMaterials.end() )
                {
                    for( WORD i = 0; i < faceCount; ++i )
                    {
                        WORD faceIndex = GetWORD(ptrChunk);
                        faceIndexRef.push_back(faceIndex);
                    
                        std::vector<IMesh::Face>::iterator iter;
                        if( m_pCurrentMesh->getFace( faceIndex, iter ) )
                        {
                            iter->pMaterial = *material;
                        }
                
                        ptrChunk += sizeof(WORD);
                    }
                }
            }
            if( ptrChunk != endPtr )
            {
                //std::cout << "error" << std::endl;
            }
        }
        else
        if( pChunk->chunkId == EDIT_AMBIENT )
        {
            ptrChunk += pChunk->chunkLength;
        }
        else
        if( pChunk->chunkId == EDIT_MATERIAL )
        {
            Chunk materialChunk;
            readMaterialChunk(ptrChunk, &materialChunk );
        }
        else
        if( pChunk->chunkId == KEYF3DS )
        {
            Chunk keyframeChunk;
            bSuccess = readKeyframerChunk( ptrChunk, &keyframeChunk );
        }
        else
        if( pChunk->chunkId == EDIT_SHADOWMAP_BIAS )
        {
            ptrChunk = pChunk->ptrChunkData;

            float fShadowMapBias = GetFloat(ptrChunk);
            ptrChunk += sizeof(DWORD);
        }
        else
        if( pChunk->chunkId == EDIT_SHADOWMAP_SIZE )
        {
            ptrChunk = pChunk->ptrChunkData;

            WORD ShadowMapSize = GetWORD(ptrChunk);
            ptrChunk += sizeof(WORD);
        }
        else
        if( pChunk->chunkId == EDIT_SHADOWMAP_SAMPLE_RANGE )
        {
            ptrChunk = pChunk->ptrChunkData;

            float fShadowMapSampleRange = GetFloat(ptrChunk);
            ptrChunk += sizeof(DWORD);
        }
        else
        if( pChunk->chunkId == EDIT_RAYTRACE_BIAS )
        {
            ptrChunk = pChunk->ptrChunkData;

            float fRaytraceMapBias = GetFloat(ptrChunk);
            ptrChunk += sizeof(DWORD);
        }
        else
        if( pChunk->chunkId == EDIT_O_CONSTS )
        {
            ptrChunk += pChunk->chunkLength;

            /*float fShadowMapSampleRange = GetFloat(ptrChunk);
            ptrChunk += sizeof(DWORD);*/
        }
        else
        if( pChunk->chunkId == TRI_VISIBLE )
        {
            BYTE triVisible = GetBYTE(pChunk->ptrChunkData);
            ptrChunk += pChunk->chunkLength;
        }
        else
        if( pChunk->chunkId == TRI_SMOOTH )
        {
            BYTE triVisible = GetBYTE(pChunk->ptrChunkData);
            ptrChunk += pChunk->chunkLength;
        }
        else
        if( pChunk->chunkId == TRI_LOCAL )
        {
            BYTE triVisible = GetBYTE(pChunk->ptrChunkData);
            ptrChunk += pChunk->chunkLength;
        }
        else
        if( pChunk->chunkId == OBJ_CAMERA )
        {
            Omen::Vector3d cameraPos, cameraTarget;
            GetVector( pChunk->ptrChunkData, cameraPos );
            GetVector( pChunk->ptrChunkData+12, cameraPos );
            float cameraBank = GetFloat( pChunk->ptrChunkData+24 );
            float cameraLens = GetFloat( pChunk->ptrChunkData+28 );
            ptrChunk += pChunk->chunkLength;
            Omen::ICamera* pNewCamera = GetOmenFactory().newCamera();
            pNewCamera->setPosition( cameraPos );
            pNewCamera->setLookAt( cameraTarget );
            
            m_pCurrentScene->addCamera(pNewCamera);
        }
        else
        if( pChunk->chunkId == OBJ_LIGHT )
        {
            Omen::Vector3d lightPos;
            GetVector( pChunk->ptrChunkData, lightPos );
            ptrChunk += pChunk->chunkLength;
        }
        else // Unkown data Chunk
        {
            std::wstring error;
            std::wstringstream strin(error);
            error.append( L"Unkown 3ds subchunk: " );
            strin << std::hex << pChunk->chunkId << std::dec << std::endl;
        
            MessageBox( NULL, error.c_str(), L"Error reading 3ds subchunk!", MB_OK );
            return false;
        }
    } while( ptrChunk < bufLast );

    return bSuccess;
}

bool Model_3DS_Reader::readKeyframerChunk( BYTE*& ptrChunk, Chunk* pChunk )
{
    bool bSuccess = true;
    getChunkData(ptrChunk, pChunk);

    unsigned long bufDelta = ptrChunk - this->m_buffer;

    ptrChunk += pChunk->chunkLength; //pChunk->ptrChunkData;


    return bSuccess;
}


bool Model_3DS_Reader::readEditorChunk( BYTE*& ptrChunk, Chunk* pChunk )
{
    bool bSuccess = false;
    getChunkData(ptrChunk, pChunk);

    m_pCurrentScene = newScene();
    m_listScenes.push_back(m_pCurrentScene);

    // Move on to next Chunk (sub-Chunk);
    ptrChunk += ChunkHeaderSize;

    Chunk subChunk;
    readSubChunk( ptrChunk, &subChunk );    

    return bSuccess;
}


bool Model_3DS_Reader::readMainChunk( BYTE*& ptrChunk, Chunk* pChunk )
{
    bool bSuccess = false;
    getChunkData(ptrChunk, pChunk);

    ptrChunk += ChunkHeaderSize; // The main Chunk doesn't have any data of is own, so move on
    
    Chunk versionChunk;
    getChunkData(ptrChunk, &versionChunk);

    if( versionChunk.chunkId == VERSION3DS )
    {
        DWORD version = GetDWORD(versionChunk.ptrChunkData);
        ptrChunk = versionChunk.ptrChunkData+sizeof(DWORD);
        if( version >= 0x3 )
        {
            bSuccess = true; // We only support version 3.0 and upwards
        }
        
    }
    return bSuccess;
}

bool Model_3DS_Reader::readNextChunk( BYTE*& ptrChunk, Chunk* pChunk )
{
    bool bSuccess = false;
    if( ptrChunk )
    {
        WORD  chunkId = GetWORD(ptrChunk);
        DWORD chunkLength = GetDWORD(ptrChunk+2);
        
        switch( chunkId )
        {
            case MAIN3DS:
            {
                Chunk c;
                if( !readMainChunk(ptrChunk, &c) )
                    return false;

                /*BYTE* ptr = ptrChunk;
                BYTE* ptrLast = m_buffer+m_bufferSize;
                if( strstr( (char*)ptr, "jpg" ) )
                {
                    ptr = (BYTE*)strstr( (char*)ptr, "jpg" );
                }
                while( ptr < ptrLast )
                {
                    char jpg_test[64];
                    memset( jpg_test, 0, 64 );
                    memcpy( jpg_test, (char*)ptr, 63 );
                    if( strstr( jpg_test, ".jpg" ) || strstr( jpg_test, ".png" ) || strstr( jpg_test, ".tga" ) )
                    {
                        ptr = (BYTE*)strstr(jpg_test, "." ) + 3;
                    }
                    ++ptr;
                }*/
                break;
            }

         //>------ Main Chunks
         case EDIT3DS       :  // this is the start of the editor config
         {
             Chunk editChunk;
             readEditorChunk( ptrChunk, &editChunk );
         }
         break;
         default:
             break;
        }

        bSuccess = true;
    }
    return bSuccess;
}

/// <summary>Get number of Scenes in the model-file</summary>
unsigned long  Model_3DS_Reader::getSceneCount() const
{
    return m_listScenes.size();
}

/// <summary>Get the Scenes</summary>
bool Model_3DS_Reader::getScenes( IScene** const& pListScenes, unsigned int maxSize ) const
{
    maxSize = min(maxSize,m_listScenes.size());
    //std::cout << "3ds file has been loaded and now user is quering the scenes" << std::endl;
    for( unsigned long iScene = 0; iScene < maxSize; ++iScene )
    {
        pListScenes[iScene] = m_listScenes.at(iScene);
        //std::cout << "Scene " << std::hex << m_listScenes.at(iScene) << std::dec << " added to the list of scenes: " << pListScenes << std::dec << std::endl;
    }
    return false;
}

/// <summary>Returns the Omen Factory Interface-object</summary>
IOmenFactory& Model_3DS_Reader::factory()
{
    // Get the Omen Interface factory
    IOmenFactory& pFactory = GetOmenFactory();
    return pFactory;
}

/// <summary>Creates a new Omen::IScene object</summary>
Omen::IScene* Model_3DS_Reader::newScene()
{
    Omen::IScene* pScene = std::move(factory().newSceneObject().release());
    //std::cout << "The Model_3DS_Reader requested a new scene and got " << std::hex << pScene << std::dec << std::endl;
    return pScene;
}

/// <summary>Creates a new Omen::IModel object</summary>
Omen::IModel* Model_3DS_Reader::newModel()
{
    Omen::IModel* pModel = factory().newModelObject();
    //std::cout << "The Model_3DS_Reader requested a new model and got " << std::hex << pModel << std::dec << std::endl;
    return pModel;
}

/// <summary>Creates a new Omen::IMesh object</summary>
Omen::IMesh* Model_3DS_Reader::newMesh()
{
    Omen::IMesh* pMesh = factory().newMeshObject();
    //std::cout << "The Mesh_3DS_Reader requested a new Mesh and got " << std::hex << pMesh << std::dec << std::endl;
    return pMesh;
}



/// Data reading functions
BYTE GetBYTE( BYTE* ptr )
{
    if( ptr )
        return *ptr;
    else
        return 0;
}

WORD GetWORD( BYTE* ptr )
{
    if( ptr )
    {
        BYTE b1 = ptr[0];
        BYTE b2 = ptr[1];
        WORD word = b1 + (b2 << 8);
        return word;
    }
    else
        return 0;
}

DWORD GetDWORD( BYTE* ptr )
{
    if( ptr )
    {
        BYTE b1 = ptr[0];
        BYTE b2 = ptr[1];
        BYTE b3 = ptr[2];
        BYTE b4 = ptr[3];
        DWORD dword = b1 + (b2 << 8) + (b3 << 16) + (b4 << 24);
        return dword;
    }
    else
        return 0;
}

float GetFloat( BYTE* ptr )
{
    if( ptr )
    {
        union
        {
            DWORD i;
            float f;
        } u;

        DWORD value = GetDWORD(ptr); 
        u.i = value;
        //u.i = (LOWORD(value) << 16) + HIWORD(value); 
        float fvalue = (float)(u.f);
        //fvalue = fabs(fvalue)/fvalue * (ceilf(fabs(fvalue)*10)/10);
        return fvalue;
    }
    return 0.0f;
}

void GetVector( BYTE* ptr, Omen::Vector3d& vector )
{
    if( ptr )
    {
        vector[0] = GetFloat(ptr);
        vector[1] = GetFloat(ptr+4);
        vector[2] = GetFloat(ptr+8);
    }
}